const childProcess = require('child_process');
const iconv = require('iconv-lite');
const EventEmitter = require('events');

const DataModel = require('../DataModel');
const os = require('os');
const tools = require('../tools');
const permission = require('../../helper/Permission');
const path = require('path')

const properties = require("properties");
const fs = require('fs');
const Docker = require('dockerode');

const CODE_CONSOLE = MCSERVER.localProperty.console_encode;

class ServerProcess extends EventEmitter {

    constructor(args) {
        super(args)
        this.dataModel = undefined;
        this.process = undefined;
        this._run = false;
        //用于异步进行的锁
        this._loading = false;

    }

    // 自定义命令启动方式
    customCommandStart() {

        //暂时使用 MCSMERVER.log 目前已弃用，下版本 log4js
        MCSERVER.infoLog('Minecraft Server start', this.dataModel.name);
        MCSERVER.log(['服务器 [', this.dataModel.name, '] 启动进程:'].join(" "));
        MCSERVER.log('-------------------------------');
        MCSERVER.log(['自定义参数启动: ', this.dataModel.highCommande].join(" "));
        MCSERVER.log(['根:', this.dataModel.cwd].join(" "));
        MCSERVER.log('-------------------------------');

        if (!this.dataModel.highCommande || this.dataModel.highCommande.trim().length <= 0)
            throw new Error("自定义参数非法,无法启动服务端");
        let commandArray = this.dataModel.highCommande.split(" ");
        let javaPath = commandArray.shift();
        //过滤
        let parList = [];
        for (let k in commandArray) {
            if (commandArray[k] == '') continue;
            parList.push(commandArray[k]);
        }

        this.process =
            childProcess.spawn(javaPath, parList, this.ProcessConfig);
    }

    // 标准 Java 程序启动方式
    templateStart(onlyCommandString = false) {
        let tmpAddList = [];
        let tmpShouldList = [];

        this.dataModel.Xmx && tmpShouldList.push('-Xmx' + this.dataModel.Xmx);
        this.dataModel.Xms && tmpShouldList.push('-Xms' + this.dataModel.Xms);
        tmpShouldList.push('-Djline.terminal=jline.UnsupportedTerminal');
        tmpShouldList.push('-jar');
        tmpShouldList.push(this.dataModel.jarName);
        tmpShouldList.push('nogui');

        tmpAddList = this.dataModel.addCmd.concat(tmpShouldList);
        //过滤
        let parList = [];
        for (let k in tmpAddList) {
            if (tmpAddList[k] == '') continue;
            parList.push(tmpAddList[k]);
        }

        let commandString = this.dataModel.java + ' ' + parList.toString().replace(/,/gim, ' ');

        //是否只获取命令字符串
        if (onlyCommandString) return commandString;

        //暂时使用 MCSMERVER.log 目前已弃用，下版本 log4js
        MCSERVER.infoLog('Minecraft Server start', this.dataModel.name);
        MCSERVER.log(['服务器 [', this.dataModel.name, '] 启动进程:'].join(" "))
        MCSERVER.log('-------------------------------');
        MCSERVER.log(['启动: ', commandString].join(" "));
        MCSERVER.log(['根:', this.dataModel.cwd].join(" "));
        MCSERVER.log('-------------------------------');

        this.process = childProcess.spawn(this.dataModel.java, parList, this.ProcessConfig);
    }

    //使用 Docker API 启动进程
    async dockerStart() {
        // 命令模板与准备数据
        let stdCwd = path.resolve(this.dataModel.cwd).replace(/\\/igm, "/");

        // 采用 Docker API 进行启动与监控
        // 启动命令解析
        let startCommande = "";
        if (this.dataModel.highCommande.trim() != "")
            startCommande = this.dataModel.highCommande;
        else
            startCommande = this.templateStart(true);
        const startCommandeArray = startCommande.split(" ");
        // 端口解析
        const protocol = "tcp";
        let portmap = this.dataModel.dockerConfig.dockerPorts;
        portmap = portmap.split(":");
        if (portmap.length > 2) {
            throw new Error("不支持的多端口操作方法，参数配置端口数量错误。");
        }
        // 绑定内部暴露端口
        const ExposedPortsObj = {};
        // 绑定内部暴露端口与其对应的宿主机端口
        const PortBindingsObj = {};
        if (portmap.length == 2) {
            // 一个端口的配置项目
            ExposedPortsObj[portmap[0] + "/" + protocol] = {};
            PortBindingsObj[portmap[0] + "/" + protocol] = [{
                HostPort: portmap[1] + ""
            }];
        }
        // 输出启动消息
        MCSERVER.log('实例 [', this.dataModel.name, '] 正在启动...');
        MCSERVER.log('-------------------------------');
        MCSERVER.log("正在使用虚拟化技术启动进程");
        MCSERVER.log("命令:", startCommandeArray.join(" "));
        MCSERVER.log("开放端口:", portmap.join("->"));
        MCSERVER.log("工作目录:", stdCwd);
        MCSERVER.log('-------------------------------');

        // 模拟一个正常的 Process
        this.process = new EventEmitter();
        const process = this.process;
        const self = this;

        // 基于镜像启动虚拟化容器
        const docker = new Docker();
        let auxContainer = null;
        auxContainer = await docker.createContainer({
            Image: this.dataModel.dockerConfig.dockerImageName,
            AttachStdin: true,
            AttachStdout: true,
            AttachStderr: true,
            Tty: true,
            Cmd: startCommandeArray,
            OpenStdin: true,
            StdinOnce: false,
            ExposedPorts: ExposedPortsObj,
            HostConfig: {
                Binds: [
                    stdCwd + ":/mcsd/"
                ],
                Memory: this.dataModel.dockerConfig.dockerXmx * 1024 * 1024 * 1024,
                PortBindings: PortBindingsObj
            }
        });
        try {
            // 尝试启动容器
            await auxContainer.start();
        } catch (err) {
            this.stop();
            this.emit('exit', 0);
            throw new Error("实例进程启动失败，建议检查启动参数与设置");
        }
        // 链接容器的输入输出流
        auxContainer.attach({
            stream: true,
            stdin: true,
            stdout: true
        }, (err, stream) => {
            if (err) throw err;
            // 赋值进程容器
            process.dockerContainer = auxContainer;
            // 模拟 pid
            process.pid = auxContainer.id;
            // 对接普通进程的输入输出流
            process.stdin = stream;
            process.stdout = stream;
            process.stderr = null;
            // 模拟进程杀死功能
            process.kill = (() => {
                auxContainer.kill().then(() => {
                    auxContainer.remove().then(() => {
                        MCSERVER.log('实例', '[', self.dataModel.name, ']', '容器已强制移除');
                    });
                });
            });
            // 容器工作完毕退出事件
            auxContainer.wait(() => {
                self.emit('exit', 0);
                self.stop();
                auxContainer.remove();
            });
            // 容器流错误事件传递
            stream.on('error', (err) => {
                MCSERVER.error('服务器运行时异常,建议检查配置与环境', err);
                self.printlnStdin(['Error:', err.name, '\n Error Message:', err.message, '\n 进程 PID:', self.process.pid || "启动失败，无法获取进程。"]);
                self.stop();
                self.emit('error', err);
            });
            // 判断启动是否成功
            if (!process.pid) {
                MCSERVER.error('服务端进程启动失败，建议检查启动命令与参数是否正确');
                self.stop();
                auxContainer.remove();
                throw new Error('服务端进程启动失败，建议检查启动命令与参数是否正确');
            }

            // 开启启动状态
            self._run = true;
            self._loading = false;
            self.dataModel.lastDate = new Date().toLocaleString();

            // 输出事件的传递
            process.stdout.on('data', (data) => self.emit('console', iconv.decode(data, self.dataModel.oe)));

            // 产生事件开启
            self.emit('open', self);

            // 输出开服资料
            self.printlnCommandLine('服务端 ' + self.dataModel.name + " 执行开启命令.");
        });
    }

    // 统一服务端开启入口
    // 不论是通过哪种方式启动，必须从这个入口进入，再根据不同配置进行分支
    start() {
        // 服务端时间权限判断
        let timeResult = this.isDealLineDate();
        if (timeResult) {
            throw new Error('服务端于 ' + this.dataModel.timeLimitDate + ' 时间已到期，拒绝启动，请咨询管理员。');
        }

        // 防止重复启动
        if (this._run || this._loading) throw new Error('服务端进程在运行或正在加载..');

        this._loading = true;

        let jarPath = this.dataModel.jarName;
        if (!path.isAbsolute(jarPath)) {
            jarPath = (this.dataModel.cwd + '/' + this.dataModel.jarName)
        }
        jarPath = jarPath.replace(/\/\//igm, '/');

        // 选择启动方式 自定义命令与配置启动
        if (!this.dataModel.highCommande) {
            // 只在非自定义模式下检查参数
            if (!fs.existsSync(this.dataModel.cwd)) {
                this.stop();
                throw new Error('服务端根目录 "' + jarPath + '" 不存在!');
            }
            if (this.dataModel.jarName.trim() == '') {
                this.stop();
                throw new Error('未设置服务端核心文件,无法启动服务器');
            }
            if (!fs.existsSync(jarPath)) {
                this.stop();
                throw new Error('服务端文件 "' + jarPath + '" 不存在或错误!');
            }

        } else {
            // 自定义模式检查
            // 检查是否准许自定义命令
            if (!MCSERVER.localProperty.customize_commande) {
                this.stop();
                throw new Error('操作禁止！管理员禁止服务器使用自定义命令！');
            }
        }

        this.ProcessConfig = {
            cwd: this.dataModel.cwd,
            stdio: 'pipe'
        }

        try {
            if (this.dataModel.dockerConfig.isDocker) {
                // Docker 启动，异步函数
                // 选用虚拟化技术启动后，将不再执行下面代码逻辑，由专属的进程启动方式启动。
                this.dockerStart().then(undefined, (error) => {
                    // Docker 启动时异常处理
                    MCSERVER.error('此服务器启动时异常,具体错误信息:', error);
                    this.printlnCommandLine("进程实例启动时失败，建议检查配置文件与启动参数");
                    this.stop();
                });
                // 阻止继续运行下去
                return true;
            } else {
                // 确定是自定义命令启动还是模板正常方式启动。
                this.dataModel.highCommande ? this.customCommandStart() : this.templateStart();
            }
        } catch (err) {
            this.stop();
            throw new Error('进程启动时异常:' + err.name + ":" + err.message);
        }

        // 设置启动状态
        this._run = true;
        this._loading = false;
        this.dataModel.lastDate = new Date().toLocaleString();

        // 进程事件监听
        this.process.on('error', (err) => {
            MCSERVER.error('服务器运行时异常,建议检查配置与环境', err);
            this.printlnStdin(['Error:', err.name, '\n Error Message:', err.message, '\n 进程 PID:', this.process.pid || "启动失败，无法获取进程。"]);
            this.stop();
            this.emit('error', err);
        });

        // 进程启动成功确认
        if (!this.process.pid) {
            MCSERVER.error('服务端进程启动失败，建议检查启动命令与参数是否正确，pid:', this.process.pid);
            this.stop();
            delete this.process;
            throw new Error('服务端进程启动失败，建议检查启动命令与参数是否正确');
        }

        // 输出事件的传递
        this.process.stdout.on('data', (data) => this.emit('console', iconv.decode(data, this.dataModel.oe)));
        this.process.stderr.on('data', (data) => this.emit('console', iconv.decode(data, this.dataModel.oe)));
        this.process.on('exit', (code) => {
            this.emit('exit', code);
            this.stop();
        });

        // 产生事件开启
        this.emit('open', this);

        // 输出开服信息
        this.printlnCommandLine('服务端 ' + this.dataModel.name + " 执行开启命令.");
        return true;
    }

    // 发送指令
    send(command) {
        if (this._run) {
            if (this.process.dockerContainer != null) {
                this.process.stdin.write(iconv.encode(command, this.dataModel.ie) + '\n');
            } else {
                this.process.stdin.write(iconv.encode(command, this.dataModel.ie));
                this.process.stdin.write('\n');
            }
            return true;
        }
        return true;
    }

    // 重启实例
    restart() {
        if (this._run == true) {
            this.send('stop');
            this.send('end');
            this.send('exit');

            // 开始计时重启
            let timeCount = 0;
            let timesCan = setInterval(() => {
                if (this._run == false) {
                    // 服务器关闭时 3 秒后立即重启
                    setTimeout(() => {
                        try {
                            this.start();
                        } catch (err) {
                            MCSERVER.error('服务器重启失败:', err);
                        }
                    }, 3000);
                    clearInterval(timesCan);
                }
                //60s 内服务器依然没有关闭，代表出现问题
                if (timeCount >= 60) {
                    clearInterval(timesCan)
                }
                timeCount++;

            }, 1000);

            return true;
        }
    }

    // 这并不是推荐的直接使用方式；
    // stop 方法只适用于本类调用，因为使用此方法不管是否成功停止，都必将进入停止状态；
    // 这样即有可能面板显示已经停止，但进程还在运行的情况；
    // 最好的做法是通过命令来结束。
    stop() {
        this._run = false;
        this._loading = false;

        this.send('stop');
        this.send('end');
        this.send('exit');
    }

    // 杀死进程，若是 Docker 进程则是移除容器
    kill() {
        if (this._run) {
            this.process.kill('SIGKILL');
            this._run = false;
            return true;
        }
        return false;
    }

    // 是否运行中
    isRun() {
        return this._run;
    }

    //输出一行到标准输出
    printlnStdin(line) {
        let str = ['[MCSMANAGER] [', tools.getFullTime(), ']:',
            line,
            "\r\n"
        ].join(" ");
        this.emit('console', str);
    }

    printlnCommandLine(line) {
        this.emit('console', "[MCSMANAGER] -------------------------------------------------------------- \r\n");
        this.printlnStdin(line);
        this.emit('console', "[MCSMANAGER] -------------------------------------------------------------- \r\n");
    }

    isDealLineDate() {
        let timeResult = permission.isTimeLimit(this.dataModel.timeLimitDate);
        return timeResult;
    }

}

module.exports = ServerProcess;
